Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

Node / meshtastic / Meshtastic-Android / files / docs / en / developer / test-builds.md

Displaying Rendered β€’ View raw β€’ Download

docs/en/developer/test-builds.md 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 20.65 KB

title: Test Builds & Obtainium
parent: Developer Guide
nav_order: 10
last_updated: 2026-07-29
description: Install and auto-update Meshtastic test builds from GitHub releases with Obtainium β€” channel configurations, APK selection, and the shareable config format.
aliases:
β€’ test-builds
β€’ obtainium
β€’ beta
β€’ snapshot

Test Builds & Obtainium

Obtainium installs and auto-updates Android apps straight from their GitHub releases β€” no Play Store account, no testing-track invite. This page sets it up for Meshtastic and shows how to follow the open beta, closed beta, or snapshot channels.

β”‚ Heads up β€” signatures. Builds from GitHub are signed with the project's release key (T383838CN=Kevin
β”‚ Hester, O=Geeksville Industries), not Google Play's. If you already have Meshtastic installed
β”‚ from the Play Store, Android will refuse to update over it: uninstall the Play Store version
β”‚ first (this clears app data), then install via Obtainium and stay on Obtainium for updates.
β”‚ Switching between the T383838fdroid and T383838google flavors is fine. Both flavors in a given release carry
β”‚ the same release key, so Obtainium can move you from one to the other in place β€” verified by
β”‚ installing the T383838google APK over an installed T383838fdroid build. Only the origin of a build (Play vs
β”‚ GitHub) determines whether the signatures clash.


Which channels can Obtainium reach?

Meshtastic for Android promotes builds up a ladder: T383838closed β†’ open β†’ production. Each maps to a Google Play track, and the GitHub release is published (un-drafted) when a build is promoted to closed or higher.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Channel β”‚ Play track β”‚ On GitHub β”‚ Obtainium can install it? β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ stable β”‚ Production β”‚ published, marked Latest β”‚ βœ… Yes β”‚
β”‚ open beta β”‚ Beta (Open) β”‚ published prerelease, tag T383838vX.Y.Z-op… β”‚ βœ… Yes β”‚
β”‚ closed beta β”‚ Alpha (Closed) β”‚ published prerelease, tag T383838vX.Y.Z-cl… β”‚ βœ… Yes β”‚
β”‚ snapshot β”‚ β€” (not on Play) β”‚ rolling prerelease, tag T383838snapshot β”‚ βœ… Yes β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Each promotion cuts its own tag, and older ones stay published β€” a whole run of T383838vX.Y.Z-open.N tags accumulates during a version cycle, each still installable with APKs attached. So a channel gathers releases rather than moving a single one forward, and the channel configs below pick the newest release whose title matches the channel.

Which release each channel currently resolves to is deliberately not listed on this page: it is archived per release tag and bundled into the app, so a live version number here would be frozen at build time and wrong within weeks. The project README carries the current state, refreshed automatically.

A channel can legitimately be empty β€” most often T383838-closed, early in a cycle before any build has been cut to it. The number of published T383838fdroid ABI splits has also changed over time (2.7.14 shipped five, 2.8.0 ships three), which is why the filter matches every split rather than naming ABIs.

Snapshot is different: it's an automated debug build of the latest commit on T383838main, rebuilt and re-published under the single moving T383838snapshot tag on every push. It never goes to Play. Because debug builds use a T383838.debug application-ID suffix (T383838com.geeksville.mesh.fdroid.debug / T383838com.geeksville.mesh.google.debug) and the debug signing key, a snapshot installs as its own separate app β€” it sits alongside a Play/stable/beta install, so the uninstall-first warning above does not apply to it.


One-tap setup

Tap a link below on the device that has Obtainium installed. Every setting from the rest of this page is already baked into it β€” pick the channel you want and the flavor you want (which flavor?).

<!-- BEGIN GENERATED LINKS: obtainium/generate-links.py -->

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Channel β”‚ `BT383838`Fdddgo`f`b… β”‚ `BT383838`Fdddfdroid`f`b flavor β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Stable β”‚ ! β€” T383838google stable + both snapshots
β€’ meshtastic-obtainium-export-fdroid.json β€” T383838fdroid stable + both snapshots

How many entries can you have at once?

Obtainium keys apps by application ID, so the rule follows from ours:


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ Application ID β”‚ Can coexist? β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Release builds β€” stable, open, close… β”‚ T383838com.geeksville.mesh β”‚ No β€” pick exactly one β”‚
β”‚ Snapshot, T383838google β”‚ T383838com.geeksville.mesh.google.debug β”‚ Yes β”‚
β”‚ Snapshot, T383838fdroid β”‚ T383838com.geeksville.mesh.fdroid.debug β”‚ Yes β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

So one release entry, plus up to both snapshots, is the maximum. Tapping a second release link switches that single entry to the new channel or flavor instead of adding one, and importing both files in turn leaves you with whichever flavor's release entry came last. The snapshots are unaffected either way.

To set things up by hand instead, follow the rest of this page.


Manual setup

1. Install Obtainium (GitHub releases or F-Droid / IzzyOnDroid).
2. Tap Add App.
3. App Source URL: T383838https://github.com/meshtastic/Meshtastic-Android
4. Set the options for the channel you want (below).
5. Tap Add, then Install.

Stable

β€’ Include prereleases: off
β€’ (optional, stricter) Verify the 'latest' tag: on
β€’ Filter APKs by regular expression: see Picking the APK

Open beta

β€’ Include prereleases: on
β€’ Filter release titles by regular expression: T383838-open
β€’ Filter APKs by regular expression: see Picking the APK

Closed beta

β€’ Include prereleases: on
β€’ Filter release titles by regular expression: T383838-closed
β€’ Filter APKs by regular expression: see Picking the APK

Bleeding edge (newest promoted test build, any channel)

β€’ Include prereleases: on
β€’ Filter release titles by regular expression: T383838-(closed|open)
β€’ Filter APKs by regular expression: see Picking the APK

Obtainium installs the newest promoted prerelease β€” whatever is currently in open or closed. The title filter is required to skip the always-newer T383838snapshot prerelease; without it Obtainium would follow snapshot instead.

Snapshot (latest commit on T383838main)

β€’ Include prereleases: on
β€’ Filter release titles by regular expression: T383838^Snapshot
β€’ Use the latest asset date as the release date: on
β€’ Version detection: off, with Release date as version on
β€’ Filter APKs by regular expression: debug-signed names, see below

Follows T383838main directly β€” updates on every push. These are debug builds (T383838.debug package, debug key), so they install as a separate app and won't disturb a stable/beta install. The T383838snapshot tag never changes, so there is no version string to compare β€” date-based pseudo-versioning is what makes updates detectable.

The APKs are named T383838…-debug-<versionCode>.apk (not T383838-release.apk), so use debug-suffixed filters:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ You want β”‚ Regex β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ T383838google flavor, most phones (arm64) β”‚ T383838google-arm64-v8a-debug-d+.apk β”‚
β”‚ T383838fdroid flavor, most phones (arm64) β”‚ T383838fdroid-arm64-v8a-debug-d+.apk β”‚
β”‚ T383838fdroid flavor, one-size-fits-all β”‚ T383838fdroid-universal-debug-d+.apk β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Snapshot releases attach only the debug APKs β€” no T383838.aab or desktop installers.

β”‚ If you build debug locally, uninstall your local build first. CI signs snapshots with its own
β”‚ debug keystore, not yours (T38383807c16b98… from CI versus T38383888914c61… from a local machine, for example).
β”‚ Since both produce the same T383838com.geeksville.mesh.<flavor>.debug package name, Android refuses the
β”‚ swap with T383838INSTALL_FAILED_UPDATE_INCOMPATIBLE. The "installs as a separate app" note above holds
β”‚ against release builds, not against your own debug builds.

Two more things about release selection apply to every channel above, not just snapshot:

β”‚ Leave "Fallback to older releases" ON. It is not a channel-strictness setting, and turning it off
β”‚ breaks every channel config here. Obtainium's release loop breaks out after the first candidate
β”‚ when fallback is off, so a title filter never gets to skip past releases that don't match β€” a
β”‚ config pinned to T383838-open or T383838^Snapshot then fails with "Could not find a suitable release" because
β”‚ production T383838v2.7.14 sits at the head of the list. The title filter is what pins the channel;
β”‚ fallback is what lets Obtainium walk down to the newest release that matches it. If your channel
β”‚ filter still finds nothing: that channel has no published release yet β€” common early in a version
β”‚ cycle. Check the project README for what is
β”‚ live, or use the Bleeding edge form to follow whichever of open/closed is newest.


Picking the APK

Each release attaches the T383838google flavor APK, several T383838fdroid flavor APKs, plus an T383838.aab (not installable) and the desktop installers. Obtainium ignores non-APK assets on its own, but the two flavors share the T383838com.geeksville.mesh package name, so exactly one must be pinned with Filter APKs by regular expression:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ You want β”‚ Regex β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Recommended for testing β€” T383838google flavor β”‚ T383838google-release.apk β”‚
β”‚ T383838fdroid flavor, most phones (arm64) β”‚ T383838fdroid-arm64-v8a-release.apk β”‚
β”‚ T383838fdroid flavor, one-size-fits-all β”‚ T383838fdroid-universal-release.apk β”‚
β”‚ T383838fdroid flavor, let Obtainium pick the ABI β”‚ T383838fdroid-.*-release.apk with Auto-select by architect… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Use the T383838google flavor while testing. It ships Firebase Crashlytics and Datadog RUM, so the crashes and errors you hit get reported back to the team β€” which is the whole point of a test phase. It also has Google push (FCM) and Google Maps. Pick an T383838fdroid flavor only if you'd rather not send that telemetry. You can change your mind later: both flavors share the release key, so Obtainium updates across the flavor boundary without an uninstall. Expect the T383838google flavor to show its analytics-consent screen the first time it starts.


Version detection turns itself off β€” that's expected

Release tags read T383838vX.Y.Z, while our installed T383838versionName reads T383838X.Y.Z (versionCode) <flavor>. Obtainium tries to reconcile the two against a list of standard version formats, all of which are fully anchored β€” none of them tolerates the spaces and parentheses in our T383838versionName. So on the first update check Obtainium logs T383838Could not reconcile version formats for: com.geeksville.mesh, sets Version detection to off for the app, and records the release tag as the installed version.

This is harmless and you should not try to fix it. With detection off, Obtainium compares the release tag it last installed against the newest tag, so updates are still detected and installed normally. What you lose is only the ability to notice a version installed outside Obtainium.

In particular, do not set "Trim the version string" to T383838\d+\.\d+\.\d+ hoping to fix it. That setting only rewrites the version parsed from the release; the unparseable side is the installed T383838versionName, which it never touches β€” so reconciliation fails exactly as before. It also adds a failure mode: if the regex ever matches nothing (a tag without a three-part version), the update check fails outright with a "no version found" error instead of degrading quietly.

Snapshot is a separate case: its tag is the literal string T383838snapshot and never changes, so tag comparison can't work either. Those configs use date-based pseudo-versioning (Release date as version plus Use the latest asset date as the release date) instead.


The config format

An Obtainium app configuration is a flat JSON object. Four keys are required β€” T383838id (Android package name), T383838url (the GitHub repo, which is what selects the source), T383838author, and T383838name. Everything else goes in T383838additionalSettings, a JSON string nested inside the JSON, so its quotes and backslashes are escaped one extra level. Any key you omit takes its default.

The same object travels in three shapes:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Shape β”‚ Format β”‚ Used for β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Import/export file β”‚ T383838{"apps":[…],"settings":null} β”‚ Import/Export β†’ Obtain… β”‚
β”‚ Deep link β”‚ T383838obtainium://app/<percent-encoded JSON> β”‚ one-tap add with setti… β”‚
β”‚ Config-site entry β”‚ T383838{"configs":[…],"icon","categories","description"} β”‚ PRs to apps.obtainiu for the full key reference and the submission checklist.


Notes

β€’ Play Protect may interrupt the install with "App scan recommended β€” Play Protect hasn't seen this app before," offering Scan app or Don't install app. Scan app uploads the APK to Google. Declining cancels the install, so on a device with Play Services you'll need to allow the scan, or install with T383838adb install instead. This showed up for a debug-signed snapshot but not for a release-signed build.
β€’ Obtainium's first launch shows two full-screen dialogs (a welcome note and a Play-certification note). They swallow an incoming T383838obtainium:// link, so dismiss them before using a one-tap link.
β€’ Track without installing: turn on Track-only to get update notifications without Obtainium downloading anything.
β€’ Snapshot filenames carry the versionCode so that a moving tag, which reuses the same release and asset URLs forever, still produces a distinguishable asset on every build.

Served by rngit 1.5.0 - Generated in 0.26s